package org.nanhill.gecko;

import java.io.File;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SpiderConfig implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1387384439917135363L;

	private static final Log logger = LogFactory.getLog(SpiderConfig.class);

	private File saveRootDirectory;

	private File mailtoLogFile;

	private boolean refreshHTMLs;

	private boolean refreshImages;

	private boolean refreshOthers;

	private Set<String> htmlExtensions;

	private Set<String> imageExtensions;

	private URL startLocation;

	private String urlMatch;

	private List interestingURLSubstrings;

	private List boringURLSubstrings;

	private boolean depthFirst;

	private int maxDepth;

	private String userAgent;

	private String basicAuthUser;

	private String basicAuthPassword;

	private int spiderThreads;

	private long checkpointInterval;

	private static final int DEF_QUEUE_CHECK_INTERVAL = 1000;

	private int queueCheckInterval;

	private static final int DEF_SPIDER_STOP_PAUSE = 500;

	private int spiderStopPause;

	private String checkpointFile;

	/**
	 * Create a default config.
	 */
	public SpiderConfig() {

		saveRootDirectory = new File(".");
		mailtoLogFile = new File("mailto.txt");

		refreshHTMLs = true;
		refreshImages = false;
		refreshOthers = false;

		htmlExtensions = new HashSet<String>();
		htmlExtensions.add("htm");
		htmlExtensions.add("html");
		htmlExtensions.add("shtml");

		imageExtensions = new HashSet<String>();
		imageExtensions.add("jpg");
		imageExtensions.add("gif");
		imageExtensions.add("png");

		urlMatch = null;
		interestingURLSubstrings = new ArrayList();
		boringURLSubstrings = new ArrayList();
		depthFirst = false;
		maxDepth = 0;

		userAgent = "WebLech Spider 0.01alpha";
		basicAuthUser = "";
		basicAuthPassword = "";

		spiderThreads = 1;

		checkpointInterval = 0;

		queueCheckInterval = DEF_QUEUE_CHECK_INTERVAL;

		spiderStopPause = DEF_SPIDER_STOP_PAUSE;

		checkpointFile = "spider.checkpoint";
	}

	/**
	 * Create a config from a java.util.Properties object.
	 */
	public SpiderConfig(Properties props) {
		// init default config
		this();

		saveRootDirectory = new File(props
				.getProperty("saveRootDirectory", "."));
		if (!saveRootDirectory.exists()) {
			if (!saveRootDirectory.mkdirs()) {
				logger.error("Couldn't create root directory: "
						+ saveRootDirectory);
				logger.info("Defaulting to . instead");
				saveRootDirectory = new File(".");
			}
		} else if (!saveRootDirectory.isDirectory()) {
			logger.error("Save root is not a directory: " + saveRootDirectory);
			logger.info("Defaulting to . instead");
			saveRootDirectory = new File(".");
		}

		String mailtoFileStr = props.getProperty("mailtoLogFile", "mailto.txt");
		// Check if absolute or relative name given
		if (mailtoFileStr.indexOf(":") != -1 || mailtoFileStr.startsWith("/")
				|| mailtoFileStr.startsWith("\\")) {
			logger.debug("Using absolute file name " + mailtoFileStr);
			mailtoLogFile = new File(mailtoFileStr);
		} else {
			logger.debug("Constructing relative file name "
					+ saveRootDirectory.getPath() + "/" + mailtoFileStr);
			mailtoLogFile = new File(saveRootDirectory.getPath() + "/"
					+ mailtoFileStr);
		}

		refreshHTMLs = Boolean.valueOf(
				props.getProperty("refreshHTMLs", "true")).booleanValue();
		refreshImages = Boolean.valueOf(
				props.getProperty("refreshImages", "false")).booleanValue();
		refreshOthers = Boolean.valueOf(
				props.getProperty("refreshOthers", "false")).booleanValue();

		htmlExtensions = parseSet(props.getProperty("htmlExtensions",
				"htm,html,shtml"));
		imageExtensions = parseSet(props.getProperty("imageExtensions",
				"jpg,gif,png"));

		String startLocStr = props.getProperty("startLocation");
		if (startLocStr != null) {
			try {
				startLocation = new URL(startLocStr);
			} catch (MalformedURLException murle) {
				if (logger.isErrorEnabled())
					logger
							.error(
									"Caught MalformedURLException parsing start URL '"
											+ startLocStr + "' : "
											+ murle.getMessage(), murle);
			}
		} else {
			logger.warn("startLocation not found in properties");
		}

		urlMatch = props.getProperty("urlMatch");

		interestingURLSubstrings = parsePropCommaSeparated(props
				.getProperty("interestingURLs"));
		boringURLSubstrings = parsePropCommaSeparated(props
				.getProperty("boringURLs"));

		depthFirst = Boolean.valueOf(props.getProperty("depthFirst", "false"))
				.booleanValue();
		try {
			String maxDepthStr = props.getProperty("maxDepth", "0");
			maxDepth = Integer.parseInt(maxDepthStr);
		} catch (NumberFormatException nfe) {
			if (logger.isErrorEnabled())
				logger
						.error(
								"Caught number format exception parsing max depth, defaulting to 1",
								nfe);
			maxDepth = 1;
		}

		userAgent = props.getProperty("userAgent", "");
		basicAuthUser = props.getProperty("basicAuthUser", "");
		basicAuthPassword = props.getProperty("basicAuthPassword", "");

		try {
			String threadsStr = props.getProperty("spiderThreads");
			spiderThreads = Integer.parseInt(threadsStr);
		} catch (NumberFormatException nfe) {
			if (logger.isErrorEnabled())
				logger
						.error(
								"Caught number format exception parsing number of threads, defaulting to 1",
								nfe);
		}

		try {
			String intervalStr = props.getProperty("checkpointInterval", "0");
			checkpointInterval = Long.parseLong(intervalStr);
		} catch (NumberFormatException nfe) {
			if (logger.isErrorEnabled())
				logger
						.error(
								"Caught number format exception parsing checkpoint interval, defaulting to 0",
								nfe);
		}

		try {
			String queueCheckIntervalStr = props.getProperty(
					"queueCheckInterval", "0");
			queueCheckInterval = Integer.parseInt(queueCheckIntervalStr);
		} catch (NumberFormatException nfe) {
			if (logger.isErrorEnabled())
				logger.error(
						"Caught number format exception parsing queue check interval, defaulting to "
								+ DEF_QUEUE_CHECK_INTERVAL, nfe);
		}

		try {
			String spiderStopPauseStr = props.getProperty("spiderStopPause",
					"0");
			spiderStopPause = Integer.parseInt(spiderStopPauseStr);
		} catch (NumberFormatException nfe) {
			if (logger.isErrorEnabled())
				logger.error(
						"Caught number format exception parsing spider stop pause, defaulting to "
								+ DEF_SPIDER_STOP_PAUSE, nfe);
		}

		checkpointFile = props.getProperty("checkpointFile", "");
	}

	private List parsePropCommaSeparated(String str) {
		ArrayList<String> result = new ArrayList<String>();
		if (str != null && str.length() > 0) {
			StringTokenizer tok = new StringTokenizer(str, ",");
			while (tok.hasMoreTokens()) {
				result.add(tok.nextToken());
			}
		}
		return result;
	}

	public void setRefreshHTMLs(boolean refreshHTMLs) {
		this.refreshHTMLs = refreshHTMLs;
	}

	public boolean refreshHTMLs() {
		return refreshHTMLs;
	}

	public void setRefreshImages(boolean refreshImages) {
		this.refreshImages = refreshImages;
	}

	public boolean refreshImages() {
		return refreshImages;
	}

	public void setRefreshOthers(boolean refreshOthers) {
		this.refreshOthers = refreshOthers;
	}

	public boolean refreshOthers() {
		return refreshOthers;
	}

	public void setSaveRootDirectory(File saveRootDirectory) {
		this.saveRootDirectory = saveRootDirectory;
	}

	public File getSaveRootDirectory() {
		return saveRootDirectory;
	}

	public void setMailtoLogFile(File mailtoLogFile) {
		this.mailtoLogFile = mailtoLogFile;
	}

	public File getMailtoLogFile() {
		return mailtoLogFile;
	}

	public void setStartLocation(URL startLocation) {
		this.startLocation = startLocation;
	}

	public URL getStartLocation() {
		return startLocation;
	}

	public void setURLMatch(String urlMatch) {
		this.urlMatch = urlMatch;
	}

	public String getURLMatch() {
		return urlMatch;
	}

	public List getInterestingURLSubstrings() {
		return interestingURLSubstrings;
	}

	public void setInterestingURLSubstrings(List interestingURLSubstrings) {
		this.interestingURLSubstrings = interestingURLSubstrings;
	}

	public List getBoringURLSubstrings() {
		return boringURLSubstrings;
	}

	public void setBoringURLSubstrings(List boringURLSubstrings) {
		this.boringURLSubstrings = boringURLSubstrings;
	}

	public boolean isInteresting(URL u) {
		return matchURL(u, interestingURLSubstrings);
	}

	public boolean isBoring(URL u) {
		return matchURL(u, boringURLSubstrings);
	}

	private boolean matchURL(URL u, List substrings) {
		String str = u.toExternalForm();
		for (Iterator i = substrings.iterator(); i.hasNext();) {
			String substr = (String) i.next();
			if (str.indexOf(substr) != -1) {
				return true;
			}
		}
		return false;
	}

	public void setDepthFirstSearch(boolean depthFirst) {
		this.depthFirst = depthFirst;
	}

	public boolean isDepthFirstSearch() {
		return depthFirst;
	}

	public void setMaxDepth(int maxDepth) {
		this.maxDepth = maxDepth;
	}

	public int getMaxDepth() {
		return maxDepth;
	}

	public void setUserAgent(String userAgent) {
		this.userAgent = userAgent;
	}

	public String getUserAgent() {
		return userAgent;
	}

	public void setBasicAuthUser(String basicAuthUser) {
		this.basicAuthUser = basicAuthUser;
	}

	public String getBasicAuthUser() {
		return basicAuthUser;
	}

	public void setBasicAuthPassword(String basicAuthPassword) {
		this.basicAuthPassword = basicAuthPassword;
	}

	public String getBasicAuthPassword() {
		return basicAuthPassword;
	}

	public void setSpiderThreads(int spiderThreads) {
		this.spiderThreads = spiderThreads;
	}

	public int getSpiderThreads() {
		return spiderThreads;
	}

	public void setCheckpointInterval(long interval) {
		this.checkpointInterval = interval;
	}

	public long getCheckpointInterval() {
		return checkpointInterval;
	}

	public int getQueueCheckInterval() {
		return queueCheckInterval;
	}

	public void setQueueCheckInterval(int queueCheckInterval) {
		this.queueCheckInterval = queueCheckInterval;
	}

	public int getSpiderStopPause() {
		return spiderStopPause;
	}

	public void setSpiderStopPause(int spiderStopPause) {
		this.spiderStopPause = spiderStopPause;
	}

	public String getCheckpointFile() {
		return checkpointFile;
	}

	public String toString() {
		return "depthFirst:\t" + depthFirst + "\nmaxDepth:\t" + maxDepth
				+ "\nhtmlExtensions:\t" + fromSet(htmlExtensions)
				+ "\nimageExtensions:\t" + fromSet(imageExtensions)
				+ "\nrefreshHTMLs:\t" + refreshHTMLs + "\nrefreshImages:\t"
				+ refreshImages + "\nrefreshOthers:\t" + refreshOthers
				+ "\nsaveRootDirectory:\t" + saveRootDirectory
				+ "\nstartLocation:\t" + startLocation + "\nurlMatch:\t"
				+ urlMatch + "\nuserAgent:\t" + userAgent
				+ "\nbasicAuthUser:\t" + basicAuthUser
				+ "\nbasicAuthPassword:\t" + "***" + "\nspiderThreads:\t"
				+ spiderThreads + "\ncheckpointInterval:\t"
				+ checkpointInterval;
	}

	private Set<String> parseSet(String str) {
		if (logger.isDebugEnabled())
			logger.debug("parseSet(" + str + ")");
		HashSet<String> result = new HashSet<String>();
		StringTokenizer sTok = new StringTokenizer(str, ",");
		while (sTok.hasMoreTokens()) {
			String tok = sTok.nextToken().trim();
			result.add(tok);
		}
		return result;
	}

	private String fromSet(Set s) {
		StringBuffer sb = new StringBuffer();
		boolean first = true;
		for (Iterator i = s.iterator(); i.hasNext();) {
			String str = (String) i.next();
			if (first) {
				first = false;
			} else {
				sb.append(",");
			}
			sb.append(str);
		}
		return sb.toString();
	}

}
